/***************************************************
	FileName: parser.c
	Purpose: Create a separate set of functions that can be used as modular
        code for common C features related to shell processing.
	Description: Basic shell functions
	Modified by Grant Meyers
***************************************************/
#include "parser.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/wait.h>

/* Static Variables Referenced only in this file */
static int pipefd[2];
static int background;

static struct shell_var shell_vars = {NULL, NULL, (char *)0x0, (char *)0x0};//Assignment 5

void parse(char *cmdline)
{
  char *cmdpart[2];

  pipefd[0] = PARSE_NOPIPE;    /* Init: default is no pipe */

  background = checkbackground(cmdline);
  
  /* Separate into individual commands if there is a pipe symbol. */

  if (strstr(cmdline, "|"))
    pipefd[0] = PARSE_USEPIPE;
  
  /* Must do the strtok() stuff before calling parse_cmd because
     strtok is used in parse_cmd or the functions parse_cmd calls. */

  cmdpart[0] = strtok(cmdline, "|");
  cmdpart[1] = strtok((char *)NULL, "|");
  parse_cmd(cmdpart[0]);
  if (cmdpart[1]) parse_cmd(cmdpart[1]);
}

/* parse_cmd will do what is necessary to separate out cmdpart and run
   the specified command. */

void parse_cmd(char *cmdpart)
{
  int setoutpipe = 0;        /* TRUE if need to set up output pipe
                   after forking */
  int pid;            /* Set to pid of child process */
  int fd;            /* fd to use for input redirection */

  char *args[MAXARGS + 5];
  char *filename;            /* Filename to use for I/O redirection */

  /////////////Assignment 5 new function///////////////////
  if(process_shell_var(cmdpart) == (char *)0x1) { return; }
  ////////////////////////////////////////////////////////
  splitcmd(cmdpart, args);
  if (pipefd[0] == PARSE_USEPIPE) {
    //Error Checking 1
    if(pipe(pipefd) == -1) {
      perror("Error pipe.  parser.c line 73");
      exit(EXIT_FAILURE);
    }
    //
    setoutpipe = 1;
  }

  pid = fork();

  //Error Checking 2
  if(pid == -1) {
    perror("Error fork.  parser.c line 84");
    exit(EXIT_FAILURE);
  }
  //
  if (!pid) {            /* child */
    if (setoutpipe) {
      //Error Checking 3
      if( dup2(pipefd[1], 1) == -1 ) {    /* connect stdout to pipe if necessary */
        perror("Error dup2.  parser.c line 92");
        exit(EXIT_FAILURE);
      }
    }
    if (!setoutpipe && (pipefd[0] > -1)) {
      /* Need to set up an input pipe. */
      //Error Checking 4
      if( dup2(pipefd[0], 0)  == -1) {
        perror("Error dups.  parser.c line 100");
        exit(EXIT_FAILURE);
      }
    }

    filename = parseredir('<', args);

    if (filename) {    /* Input redirection */
      fd = open(filename, O_RDONLY);
      if (!fd) {
       fprintf(stderr, "Couldn't redirect from %s", filename);
       exit(255);
      }
      //Error Checking 5
      if( dup2(fd, 0) == -1 ) {
        perror("Error dup2.  parser.c line 115");
        exit(EXIT_FAILURE);
      }
      //
    }

    if ((filename = parseredir('>', args))) { /* Output redirection */
      fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
      if (!fd) {
       fprintf(stderr, "Couldn't redirect to %s\n", filename);
       exit(255);
      }
      if( dup2(fd, 1) == -1 ) {
        perror("Error dup2.  parser.c line 128");
        exit(EXIT_FAILURE);
      }
    }

    if (!args[0]) {
        if(fprintf(stderr, "No program name specified.\n") < 0 ) {
        perror("Error: fprintf printed negative number of characters.  parser.c line 135");	
      }    
      exit(255);
    }
    
    if( execvp(args[0], args) == -1) {
      perror("Error execvp.  parser.c line 141");
      exit(EXIT_FAILURE);
    }
    /* If failed, die. */
    exit(255);
  } else {            /* parent */
    if ((!background) && (!setoutpipe)) {
      if(waitpid(pid, (int *)NULL, 0) == -1) {
        perror("Error waitpid.  parser.c line 149");
        exit(EXIT_FAILURE);
      }
    } else {
      if (background) {
       if( fprintf(stderr, "BG process started: %d\n", (int) pid) < 0 ) {
         perror("Error: fprintf printed negative number of characters.  parser.c line 155");
       }
      }
    if (pipefd[0] > -1) {    /* Close the pipe if necessary. */
      if (setoutpipe) {
        if(close(pipefd[1]) == -1) {
          perror("Error close.  parser.c line 161");
          exit(EXIT_FAILURE);
        }
      } else {                    
        if(close(pipefd[0]) == -1) {
          perror("Error close.  parser.c line 166");
          exit(EXIT_FAILURE);
        }
      }
    }
  }                           
  } /* if (!pid) */
  freeargs(args);
} /* parse_cmd()  */

/* splitcmd() will split a string into its component parts.

   Since splitcmd() uses strdup, freeargs() should be called on the
   args array after it is not used anymore. */

void splitcmd(char *cmdpart, char *args[])
{
  int counter = 0;
  char *tempstr;

  tempstr = strtok(cmdpart, " ");
  args[0] = (char *)NULL;
  while (tempstr && (counter < MAXARGS - 1)) {
    args[counter] = strdup(expandtilde(expand_shell_var(tempstr)));//Assignment 5
    args[counter + 1] = (char *)NULL;
    counter++;
    tempstr = strtok(NULL, " ");
  }
  if (tempstr) {         /* Broke out of loop because of num of args */
    if( fprintf(stderr, "WARNING: argument limit reached, command may be truncated.\n") < 0) {
      perror("Error: fprintf printed negative number of characters.  parser.c line 196");
      exit(EXIT_FAILURE);
    }
  }
}

/* expandtilde() will perform tilde expansion on str if necessary. */

char *expandtilde(char *str)
{
  static char retval[MAXINPUTLINE];
  char tempstr[MAXINPUTLINE];
  char *homedir;
  char *tempptr;
  int counter;
  

  if (str[0] != '~') return str;      /* No tilde -- no expansion. */
  strcpy(tempstr, (str + 1));          /* Make a temporary copy of the string */
  if ((tempstr[0] == '/') || (tempstr[0] == 0))
    tempptr = (char *)NULL;
  else {                  /* Only parse up to a slash */
    /* strtok() cannot be used here because it is being used in the function
       that calls expandtilde().  Therefore, use a simple substitute. */
    if (strstr(tempstr, "/"))
      *(strstr(tempstr, "/")) = 0;
    tempptr = tempstr;
  }
  
  if ((!tempptr) || !tempptr[0]) {    /* Get user's own homedir */
    homedir = gethomedir();
  } else {                  /* Get specified user's homedir */
    homedir = getuserhomedir(tempptr);
  }

  /* Now generate the output string in retval. */

  strcpy(retval, homedir);          /* Put the homedir in there */

  /* Now take care of adding in the rest of the parameter */

  counter = 1;
  while ((str[counter]) && (str[counter] != '/')) counter++;

  strcat(retval, (str + counter));

  return retval;
}
  
/* freeargs will free up the memory that was dynamically allocated for the
   array */
    
void freeargs(char *args[])
{
  int counter = 0;

  while (args[counter]) {
    free(args[counter]);
    counter++;
  }
}

/* Calculates number of arguments in args */

void calcargc(char *args[], int *argc)
{
  *argc = 0;
  while (args[*argc]) {
    (*argc)++;            /* Increment while non-null */
  }
  (*argc)--;            /* Decrement after finding a null */
}
  
/* parseredir will see if it can find a redirection operator oper
   in the array args[], and, if so, it will return the parameter (filename)
   to that operator. */

char *parseredir(char oper, char *args[])
{
  int counter;
  int argc;
  static char retval[MAXINPUTLINE];

  calcargc(args, &argc);

  for (counter = argc; counter >= 0; counter--) {
    if( fflush(stderr) == EOF) {
      perror("Error fflush.  parser.c line 283");
      exit(EXIT_FAILURE);
    }
    if (args[counter][0] == oper) {
      if (args[counter][1]) {    /* Filename specified without a space */
       strcpy(retval, args[counter] + 1);
       argsdelete(args + counter);
       return retval;
      } else {            /* Space seperates oper from filename */
       if (!args[counter+1]) {    /* Missing filename */
         if(fprintf(stderr, "Error: operator %c without filename", oper)<0) {
           perror("fprintf printed negative number of characters.  parser.c line 294.");
         }
         exit(255);
       }
       strcpy(retval, args[counter+1]);
       argsdelete(args + counter + 1);
       argsdelete(args + counter);
       return retval;    
      }
    }
  }
  return NULL;            /* No match */
}

/* Argsdelete will remove a string from the array */

void argsdelete(char *args[])
{
  int counter = 0;
  if (!args[counter]) return;    /* Empty argument list: do nothing */
  free(args[counter]);
  while (args[counter]) {
    args[counter] = args[counter + 1];
    counter++;
  }
}


  /////////////Assignment 5 new functions///////////////////
char *expand_shell_var(char *cmdline)
{
printf("expanding: %s\n",cmdline);
  char *tmp="";
  struct shell_var *node = NULL;

  if(cmdline[0] != '$') { return cmdline; }
  if(shell_vars.name == (char *)0x0) { return tmp; }

  node = find_llnode(&cmdline[1]);
  if(node != NULL) {
    if((tmp=malloc(strlen(node->value))) == NULL) {
      perror("Error creating temporary copy of node value. parser.c line 334");
      exit(EXIT_FAILURE);
    }
    strcpy(tmp, node->value);
  }

  return tmp;
}


char *process_shell_var(char *cmdline)
{
  char *tmp, *tmptest, *equPos, *spcPos;
  if((tmptest = malloc(strlen(cmdline))) == NULL) {
    perror("Error creating temporary copy of commandline. parser.c line 348");
    exit(EXIT_FAILURE);
  }
  if(strcpy(tmptest, cmdline)==NULL) {
    perror("Error creating temporary copy of commandline. parser.c line 352");
    exit(EXIT_FAILURE);
  }
  tmp = strtok(tmptest, " ");           //remove leading spaces on command line
  if((strncmp(tmp, "read", 4) == 0)) {  //Test for read command
    char inval[MAXINPUTLINE];

    tmp = strstr(cmdline,"read");
    strtok(tmp, " ");
    tmp = strtok(NULL, " ");

    if(fgets(inval, 1024, stdin) == NULL) {
      perror("Error fgets returned null.  parser.c line 364");
      exit(EXIT_FAILURE);
    }

    inval[strlen(inval)-1]='\0';
    if(save_shell_var(tmp, inval) != -1) {
	  free(tmptest);
      return (char *)0x1;
    } else {
      perror("Error saving shell variable. parser.c line 372");
      exit(EXIT_FAILURE);
    }
  }
  equPos = strchr(cmdline, '=');
  if((spcPos =  strchr(cmdline, ' ')) == NULL) { spcPos = equPos+1; }
  if((equPos != NULL) && (equPos < spcPos)) {
    if(equPos[1] == '"') { 
      if(equPos[strlen(equPos)-1] == '"') { equPos[strlen(equPos)-1]= '\0'; }
      equPos += 1;
    }
    if(save_shell_var(strtok(cmdline, "="), equPos+1) != -1) { 
	  free(tmptest);
	  return (char *)0x1; 
	}
  }
  return cmdline;
}

int save_shell_var(char *var_name, char *var_value)
{
  struct shell_var *tmp=NULL;

  if((shell_vars.name >= (char *)SHELL_VAR_MAX) && (strcmp(var_value, "") != 0)) { 
    perror("No more shell variables allowed.  Please overwrite one currently in use or restart shell");
    return -1;
  }

  if((tmp = find_llnode(var_name)) != NULL) {
    if(strlen(var_value) == 0) {
      remove_llnode(tmp);
      return 0;
    }
    strcpy(tmp->value, var_value);
    return 0;
  }
  
  new_llnode(var_name, var_value);
  return 0;
}

int remove_llnode(struct shell_var *node)
{
  if((node == NULL) || (node==&shell_vars)) { return 0; }
  struct shell_var *prv, *nxt;
  prv = node->prev;
  nxt = node->next;
  prv->next = nxt;
  if(nxt != NULL) { nxt->prev = prv; }
  free(node->name);
  free(node->value);
  free(node);
  shell_vars.name -= 1;

  return 0;
}

int new_llnode(char *name, char *value)
{
  struct shell_var *new_node=NULL, *end_node=NULL; 
  end_node = &shell_vars;

  new_node = end_node->next;
  while(new_node != NULL) {
    end_node = new_node;
    new_node = new_node->next;
  }
  if((new_node = malloc(sizeof(struct shell_var))) == NULL) {
    perror("Error saving shell variable. parser.c line 437");
    exit(EXIT_FAILURE);
  }
  end_node->next = new_node;
  new_node->prev = end_node;
  new_node->next = NULL;
  if((new_node->name = malloc(strlen(name))) == NULL) {
    perror("Error saving shell variable. parser.c line 444");
    exit(EXIT_FAILURE);
  }
  strcpy(new_node->name, name);
  if((new_node->value = malloc(strlen(value))) == NULL) {
    perror("Error saving shell variable. parser.c line 449");
    exit(EXIT_FAILURE);
  }
  strcpy(new_node->value, value);
  shell_vars.name += 1;
  return 0;
}

struct shell_var *find_llnode(char *var_name)
{
  if(shell_vars.name == (char *)0x0) { return NULL; }

  struct shell_var *cur_test = shell_vars.next;
  while(cur_test != NULL) {
    if(strcmp(var_name, cur_test->name) == 0) {
      return cur_test;
    }
    cur_test = cur_test->next;
  }
  return NULL;
}
  ////////////////////////////////////////////////////////









